
// Copyright (C) 2009-2011 Luca Piccioni
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program.  If not, see <http://www.gnu.org/licenses/>.

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Reflection;
using System.Runtime.InteropServices;
using System.Text;

namespace Derm.OpenGL
{
	/// <summary>
	/// Base class for loading external routines.
	/// </summary>
	/// <remarks>
	/// This class is used for basic operations of automatic generated classes Gl, Wgl and Glx. The main
	/// functions of this class allows:
	/// - To parse OpenGL extensions string
	/// - To query import functions using reflection
	/// - To query delegate functions using reflection
	/// - To link imported functions into delegates functions.
	/// </remarks>
	public class ProcLoader
	{
		#region Import Function Linkage

		/// <summary>
		/// Link delegates fields using import declarations.
		/// </summary>
		/// <param name="path">
		/// A <see cref="System.String"/> that specifies the assembly file path containing the import functions.
		/// </param>
		/// <param name="type">
		/// A <see cref="System.Type"/> that specifies the type used for detecting import declarations and delegates fields.
		/// </param>
		protected static void LinkLibraryProcImports(string path, Type type)
		{
			SortedList<string, MethodInfo> sImportMap;
			List<FieldInfo> sDelegates;

			LinkProcAddressImports(path, type, delegate(string libpath, string function) {
				return (GetProcAddress.GetAddress(libpath, function));
			}, out sImportMap, out sDelegates);
		}

		/// <summary>
		/// Link delegates fields using import declarations.
		/// </summary>
		/// <param name="type">
		/// A <see cref="System.Type"/> that specifies the type used for detecting import declarations and delegates fields.
		/// </param>
		/// <param name="sImportMap">
		/// A <see cref="SortedList{String, MethodInfo}"/> mapping a <see cref="MethodInfo"/> with the relative function name.
		/// </param>
		/// <param name="sDelegates">
		/// A <see cref="List{FieldInfo}"/> listing <see cref="FieldInfo"/> related to function delegates.
		/// </param>
		protected static void LinkOpenGLProcImports(Type type, out SortedList<string, MethodInfo> sImportMap, out List<FieldInfo> sDelegates)
		{
			LinkProcAddressImports(null, type, delegate(string libpath, string function) {
				return (GetProcAddress.GetOpenGLAddress(function));
			}, out sImportMap, out sDelegates);
		}

		/// <summary>
		/// Link delegates fields using import declarations.
		/// </summary>
		/// <param name="path">
		/// A <see cref="System.String"/> that specifies the assembly file path containing the import functions.
		/// </param>
		/// <param name="type">
		/// A <see cref="System.Type"/> that specifies the type used for detecting import declarations and delegates fields.
		/// </param>
		/// <param name="getAddress">
		/// A <see cref="GetAddressDelegate"/> used for getting function pointers. This parameter is dependent on the currently running platform.
		/// </param>
		/// <param name="sImportMap">
		/// A <see cref="SortedList{String, MethodIndo}"/> mapping a <see cref="MethodInfo"/> with the relative function name.
		/// </param>
		/// <param name="sDelegates">
		/// A <see cref="List{FieldInfo}"/> listing <see cref="FieldInfo"/> related to function delegates.
		/// </param>
		/// <remarks>
		/// <para>
		/// The type <paramref name="type"/> shall have defined a nested class named "Imports" specifying the import declarations and a nested
		/// class named "Delagates" specifying the delegate fields.
		/// </para>
		/// <para>
		/// 
		/// </para>
		/// </remarks>
		private static void LinkProcAddressImports(string path, Type type, GetAddressDelegate getAddress, out SortedList<string, MethodInfo> sImportMap, out List<FieldInfo> sDelegates)
		{
			Type impClass = type.GetNestedType("Imports", BindingFlags.Static | BindingFlags.NonPublic);
			Type delClass = type.GetNestedType("Delegates", BindingFlags.Static | BindingFlags.NonPublic);

			// Query imports declarations
			MethodInfo[] iMethods = impClass.GetMethods(BindingFlags.Static|BindingFlags.NonPublic);

			sImportMap = new SortedList<string, MethodInfo>(iMethods.Length);
			foreach (MethodInfo m in iMethods)
				sImportMap.Add(m.Name, m);

			// Query delegates declarations
			sDelegates = new List<FieldInfo>(delClass.GetFields(BindingFlags.Static | BindingFlags.NonPublic));

			foreach (FieldInfo fi in sDelegates) {
				Delegate pDelegate = null;
				string pImportName = fi.Name.Substring(1);
				IntPtr mAddr = getAddress(path, pImportName);

				if (mAddr != IntPtr.Zero) {
					// Try to load external symbol
					if ((pDelegate = Marshal.GetDelegateForFunctionPointer(mAddr, fi.FieldType)) == null) {
						MethodInfo mInfo;

						if (sImportMap.TryGetValue(pImportName, out mInfo) == true)
							pDelegate = Delegate.CreateDelegate(fi.FieldType, mInfo);
					}

					if (pDelegate != null) {
						fi.SetValue(null, pDelegate);
					}
				}
			}
		}

		private delegate IntPtr GetAddressDelegate(string path, string function);

		private static void FilterDelagatesByPlatform()
		{
			
		}

		#endregion

		#region Delegates Management

		/// <summary>
		/// Query delegate members.
		/// </summary>
		/// <param name="klass">
		/// A <see cref="Type"/> epresenting the nested class containing delegate declarations.
		/// </param>
		/// <returns>
		/// It returns an array of FieldInfo representing the delegate member.
		/// </returns>
		protected static FieldInfo[] QueryDelegates(Type klass)
		{
			return (klass.GetFields(BindingFlags.Static|BindingFlags.NonPublic));
		}

		/// <summary>
		/// Synchronize delegate fields.
		/// </summary>
		/// <param name="pImports"></param>
		/// <param name="pDelegates"></param>
		/// <param name="name"></param>
		protected static void SyncDelegate(SortedList<string, MethodInfo> pImports, FieldInfo[] pDelegates, string name)
		{
			Debug.Assert(pDelegates != null);

			foreach (FieldInfo fi in pDelegates) {
				if (fi.Name == name)
					fi.SetValue(null, GetDelegate(pImports, fi.Name, fi.FieldType));
			}
		}

		/// <summary>
		/// Synchronize delegate fields.
		/// </summary>
		/// <param name="pImports"></param>
		/// <param name="pDelegates"></param>
		protected static void SynchDelegates(SortedList<string, MethodInfo> pImports, List<FieldInfo> pDelegates)
		{
			Debug.Assert(pDelegates != null);

			foreach (FieldInfo fi in pDelegates)
				fi.SetValue(null, GetDelegate(pImports, fi.Name.Substring(1), fi.FieldType));
		}

		/// <summary>
		/// 
		/// </summary>
		/// <param name="pImports"></param>
		/// <param name="name"></param>
		/// <param name="signature"></param>
		/// <returns></returns>
		private static Delegate GetDelegate(SortedList<string, MethodInfo> pImports, string name, Type signature)
		{
			Delegate mDelegate = null;
			IntPtr mAddr = GetProcAddress.GetOpenGLAddress(name);

			if (mAddr == IntPtr.Zero)
				return (null);

			// Try to load external symbol
			if ((mDelegate = Marshal.GetDelegateForFunctionPointer(mAddr, signature)) == null) {
				MethodInfo mInfo;

				if (pImports.TryGetValue(name, out mInfo) == true)
					return (Delegate.CreateDelegate(signature, mInfo));
				else
					return (null);
			} else
				return (mDelegate);
		}

		#endregion

		#region Procedure Logging

		/// <summary>
		/// Create a file for logging procedures.
		/// </summary>
		/// <param name="path">
		/// A <see cref="System.String"/> that specifies the path of the log file.
		/// </param>
		[Conditional("DEBUG")]
		public static void CreateLog(string path)
		{
			// Create log file
			sProcLog = new StreamWriter(path);
		}

		/// <summary>
		/// Log a comment.
		/// </summary>
		/// <param name="format">
		/// A <see cref="System.String"/> that specifies the comment format string. A comment prefix
		/// is prepended.
		/// </param>
		/// <param name="args">
		/// A <see cref="T:System.Object[]"/> listing the <paramref name="format"/> string argument values.
		/// </param>
		[Conditional("DEBUG")]
		public static void LogComment(string format, params object[] args)
		{
			Debug.Assert(String.IsNullOrEmpty(format) == false);

			// Write a comment line
			LogProc(String.Format("// "+format, args));
		}

		/// <summary>
		/// Log a method using reflection.
		/// </summary>
		/// <param name="mInfo">
		/// A <see cref="MethodInfo"/> reflecting the method to log.
		/// </param>
		/// <param name="args">
		/// The method arguments, if content renderable. This parameter is optional.
		/// </param>
		[Conditional("DEBUG")]
		public static void LogMethod(MethodBase mInfo, params object[] args)
		{
			if (mInfo == null)
				throw new ArgumentNullException("mInfo");

			StringBuilder sb = new StringBuilder();
			ParameterInfo[] mInfoParams = mInfo.GetParameters();

			sb.AppendFormat("{0}.{1}(", mInfo.DeclaringType.Name, mInfo.Name);
			if (mInfoParams.Length > 0) {
				for (uint p = 0; p < mInfoParams.Length; p++) {
					if ((args != null) && (p < args.Length)) {
						if (args[p] != null)
							sb.AppendFormat("{0}, ", args[p].ToString());
						else
							sb.AppendFormat("(null) [{0}]", mInfoParams[p].ParameterType.Name);
					} else {
						sb.AppendFormat("[{0}], ", mInfoParams[p].ParameterType.Name);
					}
				}
				sb.Remove(sb.Length - 2, 2);
			}
			sb.Append(")");

			LogProc(sb.ToString());
		}

		/// <summary>
		/// Log a procedure call.
		/// </summary>
		/// <param name="format">
		/// A <see cref="System.String"/> that specifies the format string.
		/// </param>
		/// <param name="args">
		/// An array of objects that specifies the arguments of the <paramref name="format"/>.
		/// </param>
		[Conditional("DEBUG")]
		public static void LogProc(string format, params object[] args)
		{
			Debug.Assert(String.IsNullOrEmpty(format) == false);

			// Write a line
			if (sProcLog != null) {
				sProcLog.WriteLine(format, args);
				sProcLog.Flush();
			}
		}

		/// <summary>
		/// Close procedure log.
		/// </summary>
		[Conditional("DEBUG")]
		public static void CloseLog()
		{
			sProcLog.Close();
			sProcLog = null;
		}

		/// <summary>
		/// Procedure log file.
		/// </summary>
		private static StreamWriter sProcLog = null;

		#endregion
	}
}
